﻿
/// <reference path="_namespace.js" />
/// <reference path="Component.js" />
/// <reference path="ConfigurationManager.js" />
/// <reference path="MenuItemDisplayFlags.js" />

/*****************************************************************************
Ajax library class definition. Kevin Robinson 2006.
Class: ajax.components.TypeAhead
/*****************************************************************************/

ajax.lang.addNamespace("ajax.components");
ajax.lang.addNamespace("ajax.windows");
ajax.lang.imports("ajax.components.MenuItemDisplayFlags");

//-----------------------------------------------------------------------------
ajax.components.TypeAhead = function(doc, name, textboxInput, servercode, w, h, loaderRequestParams) // Constructor
{
    ajax.components.TypeAhead.superConstructor.call(this, doc);
        
    var self = this;
    
    // Store public properties
    this.name = name;    
    this.serverCode = servercode; 
    if (ajax.components.TypeAhead.ShareExtendedProperties) 
        this.extendedProperties         = ajax.components.TypeAhead.ExtendedProperties;
    else
        this.extendedProperties         = new ajax.components.TypeAhead.DefaultExtendedProperties();
    
    
    // Public Delegates that clients can subscribe to - set these to new Event Routers to allow multiple clients
    this.getEvent(this.window, "onTypeAheadSelected");    
    this.getEvent(this.window, "onListItemHighlighted");
    this.getEvent(this.window, "onListItemCreated");
    
    // store away function delegates so things get called in the correct context and
    // Can be added / removed from Events easily
    this.giveOptionsDelegate    = function(e){ ajax.debug.Output.writeLn("giveOptions"); self.giveOptions(e); };
    this.giveOptionsIfNotPendingDelegate = function(e)
    {     
        if (!self.bMadeRequest)
            self.giveOptionsDelegate(e);
        else
            ajax.debug.Output.writeLn("giveOptions suppressed due to outstanding request");
    };
    
    this.handleMovementDelegate = function(e){ self.handleMovement(e); };
    this.handleBlurDelegate     = function(e)
    {
        ajax.debug.Output.writeLn("Type ahead Box has lost focus.");
        this.hasFocus = false;
        
        if (self.extendedProperties.useTimeout)
            self.startTimeout();
        else
            self.hideWindow();  
    };   
    this.handleFocusDelegate    = function(e)
    {        
        ajax.debug.Output.writeLn("Type ahead Box has focus.");
        this.hasFocus = true;
    }
    
    
    // Public members
    this.highlightBackgroundColor   = "#000066";
    this.highlightColor             = "#FFFFFF";
    this.arrOptions                 = new Array();
    this.strLastValue               = "";   
    this.bMadeRequest               = false;    
    this.objLastActive              = null;
    this.currentValueSelected       = -1;
    this.bNoResults                 = false;
    this.isTiming                   = false;
    this.isOpera = (navigator.userAgent.toLowerCase().indexOf("opera")) != -1;
    
    
    // Contains a ContentLoader object
    this.loader  = ajax.lang.safeNew("ajax.net.ContentLoader", this, this.buildChoices, null,
        this.serverCode, "POST", loaderRequestParams);      
            
    
    // *Contains* a window...default to animated
    this.win    = ajax.lang.safeNew("ajax.windows.FlyoutWindow", this.doc, this.name, w, h);
    // We must load our window now so we can start using it.
    this.win.load();
    this.setWindowAnimated(true);
    this.win.hide();
    
    // set the textbox
    this.textBoxInput = null;
    this.setTextbox(textboxInput);    
    
    // set the event handlers for the textbox
    this.addHandler();
    
    // Create an instance of our own subclass of the configuration manager...
    this.configurationManager = new ajax.components.TypeAhead.ConfigurationManager(this);
    
}

//-----------------------------------------------------------------------------
// Inherits from Base
ajax.lang.extend("ajax.components.TypeAhead", "ajax.components.Component");










//-----------------------------------------------------------------------------
// Prototyped Methods
ajax.components.TypeAhead.prototype.setTextbox = function(box)
{
    // Add a reference to this controller object to the textbox as passed in 
    // (incase the client does not wish to store us in a global but still requires access)        
    this.textBoxInput           = box;         
    this.textBoxInput.TypeAhead = this;
    this.textBoxInput.hasFocus  = true;
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.setWindowAnimated = function(val)
{
    this.win.animated            = val;
    // We need to alter the specs for the window styles, whether or not they exist yet!   
    if (val)
    {    
        if (this.win.layer)
        {
            this.win.layer.style.overflow = "hidden";
            this.win.content.style.overflow = "auto";
        }
        else
        {
            this.win.getEvent(this.window, "onLoad").addListener(function(e)
                {
                    this.layer.style.overflow = "hidden";
                    this.content.style.overflow = "auto";
                }
            );
        }
    }
    else
    {
        if (this.win.layer)
        {
            this.win.layer.style.overflow   = "visible";
            this.win.content.style.overflow = "visible";
        }
        else
        {
            this.win.getEvent(this.window, "onLoad").addListener(function(e)
                {
                    this.layer.style.overflow   = "visible";
                    this.content.style.overflow = "visible";
                }
            );
        }
    }
}

//-----------------------------------------------------------------------------
// When the enabled state changes, we must add or remove our handlers from the textbox
ajax.components.TypeAhead.prototype.setEnabled = function(val)
{
    this.extendedProperties.enabled = val;
    if (val)
        this.addHandler();
    else
        this.removeHandler();
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.addHandler = function()
{
    var self = this;
    
    var textbox = this.textBoxInput;
    
    // Subscribe to some events of the textbox (leaving existing events in place)
    var txtOnKeyUp      = ajax.lang.getEvent(this.window, textbox, "onkeyup");
    var txtOnKeyDown    = ajax.lang.getEvent(this.window, textbox, "onkeydown");
    var txtOnFocus      = ajax.lang.getEvent(this.window, textbox, "onfocus");
    var txtOnBlur       = ajax.lang.getEvent(this.window, textbox, "onblur");
    var txtOnKeyPress   = ajax.lang.getEvent(this.window, textbox, "onkeypress");
    var txtOnClick      = ajax.lang.getEvent(this.window, textbox, "onclick");
    
    // Note: These handlers are never duplicated no matter how many times this method runs
    // because the EventRouter makes use of our extended Array objects to stop duplicates.
    txtOnKeyUp.addListener(this.giveOptionsDelegate);
    txtOnKeyDown.addListener(this.handleMovementDelegate);
    txtOnFocus.addListener(this.handleFocusDelegate);
    txtOnFocus.addListener(this.giveOptionsDelegate);
    txtOnClick.addListener(this.giveOptionsIfNotPendingDelegate);    
    txtOnBlur.addListener(this.handleBlurDelegate);
    
    if (this.isOpera)
        txtOnKeyPress.addListener(this.giveOptionsDelegate);
        
    ajax.lang.getEvent(this.window, this.doc, "onmousedown").addListener(function(e){ self.hideWindow()});
                     
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.removeHandler = function()
{
    var textbox = this.textBoxInput;
    
    var txtOnKeyUp      = ajax.lang.getEvent(this.window, textbox, "onkeyup");
    var txtOnKeyDown    = ajax.lang.getEvent(this.window, textbox, "onkeydown");
    var txtOnFocus      = ajax.lang.getEvent(this.window, textbox, "onfocus");
    var txtOnBlur       = ajax.lang.getEvent(this.window, textbox, "onblur");
    var txtOnKeyPress   = ajax.lang.getEvent(this.window, textbox, "onkeypress");
    var txtOnClick      = ajax.lang.getEvent(this.window, textbox, "onclick");
    
    txtOnKeyUp.removeListener(this.giveOptionsDelegate);
    txtOnKeyDown.removeListener(this.handleMovementDelegate);
    txtOnFocus.removeListener(this.handleFocusDelegate); 
    txtOnFocus.removeListener(this.giveOptionsDelegate);
    txtOnBlur.removeListener(this.handleBlurDelegate);
    txtOnClick.removeListener(this.giveOptionsIfNotPendingDelegate);
    
    if (this.isOpera)
        txtOnKeyPress.removeListener(this.giveOptionsDelegate);
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.onTxtBlurDelegate = function(e)
{
    if (this.extendedProperties.useTimeout)
        this.startTimeout();
    else
        this.hideWindow();        
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.startTimeout = function()
{
    var self = this;
    this.isTiming = setTimeout(function(){self.hideWindow()}, self.extendedProperties.theVisibleTime);
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.setWindowPosition = function(ele)
{
    if (!ele)
        return;
    
    var self = this;
    
    // Set window position
    this.win.setPosition(null, null, ele);
    
    //
    if (this.extendedProperties.useTimeout)
    {        
        this.win.layer.onmouseout   = function(){self.startTimeout();};
        this.win.layer.onmouseover  = function(){self.eraseTimeout();};
    }
    else
    {
        this.win.layer.onmouseout   = null;
        this.win.layer.onmouseover  = null;
    }    
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.hideWindow = function()
{    
    this.win.hide(); 
    this.currentValueSelected = -1;
    this.eraseTimeout();
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.showWindow = function()
{
    if (this.win.layer.style.visibility == "hidden")
        this.win.show(this.textBoxInput);
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.eraseTimeout = function()
{
    clearTimeout(this.isTiming);
    this.isTiming = false;
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.handleMovement = function(e)
{
    // Only move if we have content.
    if (this.bNoResults)
        return;
        
    var intKey = this.keyboard.getKeyCode(e);
    
    // Handle movement keys (UP, Down)
    if (this.objLastActive == this.textBoxInput)
    {
        if (intKey == this.keyboard.keys.KeyUp) // Up key        
        {
            this.moveHighlight(-1);
            return false;
        }
        else if (intKey == this.keyboard.keys.KeyDown) // Down key
        {
            this.moveHighlight(1);
            return false;
        }
    }
}

//-----------------------------------------------------------------------------
// GiveOptions...
ajax.components.TypeAhead.prototype.giveOptions = function(e)
{    
    
    var intKey = this.keyboard.getKeyCode(e);   
    
    if (this.extendedProperties.useTimeout)
    {
        if (this.isTiming)
            this.eraseTimeout();
        this.startTimeout();
    }
        
    
    if (this.textBoxInput.value.length == 0 && (!this.isOpera))
    {
        this.arrOptions = new Array();
        this.hideWindow();
        this.strLastValue = "";
        return false;
    }
        

    if (this.objLastActive == this.textBoxInput)
    {
        // Bomb out for movement keys
        if (intKey == this.keyboard.keys.KeyUp || intKey == this.keyboard.keys.KeyDown)
            return false;
                     
        if (intKey == this.keyboard.keys.KeyReturn) // return
        {            
            this.grabHighlighted();
            this.textBoxInput.blur();
            return false;
        }
    }
        
    
    if (this.objLastActive != this.textBoxInput
        || this.textBoxInput.value.indexOf(this.strlastValue) != 0
        || ((this.arrOptions.length == 0 || this.arrOptions.length == 15)
        && !this.bNoResults)
        || (this.textBoxInput.value.length <= this.strLastValue.length))        
    {
        this.objLastActive = this.textBoxInput;
        this.bMadeRequest = true;
        this.runTypeAhead(this.textBoxInput.value);        
    }
    else if (!this.bMadeRequest)
        this.buildList(this.textBoxInput.value);
    
    this.strLastValue = this.textBoxInput.value;
   
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.runTypeAhead = function(sText)
{   
    ajax.debug.Output.writeLn("running type ahead");
    // Configure the regular expressions based on current extended properties
    this.setRegExpressions();

    // Send the request with details about this search.    
    this.loader.sendRequest("q=" + escape(sText), "where=" + this.extendedProperties.matchAnywhere,
        "multi=" + this.extendedProperties.multi);
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.buildChoices = function(request)
{   
    // If the textbox still has focus, and has a value in it, then process the results, 
    // otherwise don't bother as they have moved on or deleted their value...
    if (this.textBoxInput.hasFocus && this.textBoxInput.value.length > 0)
    {
        var sText = request.responseText;
        this.arrOptions = eval(sText);
        this.buildList(this.strLastValue);
    }
    else
    {
        ajax.debug.Output.writeLn("HTTP message recieved but box no longer has focus or value (high server/network latency?) so supressing data processing.");
        this.hideWindow();
    }
    this.bMadeRequest = false;
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.buildList = function(sText)
{                              
    // Backslash out out the nasty chars from the input text
    sText   = sText.replace(/([^$A-Za-z0-9_\s])/gi, "\\$1");
    
    this.setWindowPosition(this.textBoxInput);
    
    // Clear any Current List
    this.win.content.innerHTML = "";
    
    // makeMatches will also add the matched items to the GUI.
    var theMatches = this.makeMatches(sText);    
    
    if (theMatches.length > 0)
    {                         
        // Again, to help with latency, don't force the current index back to 0 unless we have to        
        if (this.currentValueSelected < 1 || this.currentValueSelected >= theMatches.length)                    
            this.currentValueSelected = 0;
        
        this.setHighColor(this.win.content.childNodes[this.currentValueSelected]);
        this.bNoResults = false;
        this.showWindow();
    }
    else
    {
        this.currentValueSelected = -1;
        this.bNoResults = true;
        if (this.extendedProperties.showNoMatchMessage)        
            this.win.content.innerHTML = this.extendedProperties.noMatchingDataMessage;
        else
            this.hideWindow();
    }           
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.makeMatches = function(xCompareStr)
{    
    var matchArray = new Array();
    
    for (var i = 0; i < this.arrOptions.length; i++)
    {        
        var stringToMatch = this.arrOptions[i][0];
        
        if (this.extendedProperties.multi == false)
        {
            // Simple match with th entire input string
            var regExp = new RegExp(this.regExAny + xCompareStr, this.regExFlags);
            var theMatch = stringToMatch.match(regExp);
            if (theMatch)        
                matchArray[matchArray.length] = this.createListItem(stringToMatch, xCompareStr, i);            
        }
        else
        {
            // MultiMatch with each WORD, EACH of which MUST match
            var arrWords = xCompareStr.split(" ");
            var theMatch = true;
            for (var j = 0; j < arrWords.length; j++)
            {
                var regExp = new RegExp(this.regExAny + arrWords[j], this.regExFlags);               
                theMatch = stringToMatch.match(regExp);
                if (!theMatch)
                    break;
            }            
            // Only do this if ALL the words match
            if (theMatch)        
                matchArray[matchArray.length] = this.createListItem(stringToMatch, xCompareStr, i); 
        }
    }
    return matchArray;
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.createListItem = function(xStr, xTextMatch, xVal)
{
    var undeStart = "<span style='text-decoration: underline; font-weight: bold'>";
    var undeEnd   = "</span>";
         
    var matchedText = "";
    var arrMatchedText = null;
    var arrRegEx       = null;
    if (this.extendedProperties.multi == false)
    {  
        // Replace the escape character to fine the actual Length
        var sTextWithoutEscapedChars = xTextMatch.replace(/\\/gi, "");
        var iMatchLength = sTextWithoutEscapedChars.length;
        
        // Simple matching
        var regExp = new RegExp(this.regExAny + xTextMatch, this.regExFlags);
        var aStart = xStr.search(regExp);
        matchedText = xStr.substring(aStart, aStart + iMatchLength);
    }
    else
    {
        var arrWords    = xTextMatch.split(" ");
        arrMatchedText  = new Array();
        arrRegEx        = new Array();
        
        for (var i = 0; i < arrWords.length; i++)
        {
            if (arrWords[i].length > 0)
            {
                // Replace the escape character to fine the actual Length
                var sTextWithoutEscapedChars = arrWords[i].replace(/\\/gi, "");
                var iMatchLength = sTextWithoutEscapedChars.length;
                    
                var regExp = new RegExp(this.regExAny + arrWords[i], this.regExFlags);
                var aStart = xStr.search(regExp);
                matchedText = xStr.substring(aStart, aStart + iMatchLength);            
                arrMatchedText[arrMatchedText.length] = matchedText;
                arrRegEx[arrRegEx.length] = regExp;
            }
        }
                     
    }  
    // create the span tag
    var self = this;
    var span = this.doc.createElement("span");
    span.id             = "OptionsList_" + this.win.content.childNodes.length;
    span.theArrayNumber = xVal;
    span.style.width    = "100%";
    span.style.display  = "block";
    span.backgroundColor= this.win.layer.style.backgroundColor;    
    span.onmouseover = function(){ self.setHighColor(this); };
    span.onmousedown = function(){ self.setText(xVal); };
    // Underline the stuff that matches...
    if (arrMatchedText == null)
    {
        span.innerHTML   = xStr.replace(regExp, undeStart + matchedText + undeEnd);
    }
    else
    {
        var finalText = xStr
        for (var i = 0; i < arrMatchedText.length; i++)
            finalText   = finalText.replace(arrRegEx[i], ":::" + arrMatchedText[i] + ";;;"); 
        finalText = finalText.replace(/:::/ig, undeStart);
        finalText = finalText.replace(/;;;/ig, undeEnd);
        span.innerHTML = finalText;           
    }    
    this.win.content.appendChild(span);
    
    // Grab extra params for event
    var displayText = this.arrOptions[xVal][0];
    var primaryKey  = this.arrOptions[xVal][1];
    
    // Give the client dibs on this span tag as it's created
    this.onListItemCreated.call(this, new ajax.components.TypeAhead.onListItemEventArgs(this, span, displayText, primaryKey));
    
    return span;
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.setHighColor = function(spanTag)
{
    if (spanTag)    
        this.currentValueSelected = spanTag.id.slice(spanTag.id.indexOf("_") + 1, spanTag.id.length);            
        
    // Turn off all other highlights
    var spanTags = this.win.content.childNodes;
    var xVal = -1;
    for (var i = 0; i < spanTags.length; i++)
    {
        spanTags[i].style.backgroundColor = this.win.layer.style.backgroundColor;
        spanTags[i].style.color           = this.win.layer.style.color;
        if (spanTags[i] == spanTag)
            xVal = i;
    }
    if (spanTag)
    {
        // Highlight selected
        spanTag.style.backgroundColor = this.highlightBackgroundColor;
        spanTag.style.color           = this.highlightColor;
    }
    
    var displayText = this.arrOptions[xVal][0];
    var primaryKey  = this.arrOptions[xVal][1];
    
    // Give the client dibs on the item highlighted
    this.onListItemHighlighted.call(this, new ajax.components.TypeAhead.onListItemEventArgs(this, spanTag, displayText, primaryKey));
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.moveHighlight = function(xDir)
{
    var newValue = parseInt(this.currentValueSelected) + parseInt(xDir);
    if (newValue > -1 && newValue < this.win.content.childNodes.length)
    {
        this.currentValueSelected = newValue;
        // Get handle to the new one
        var newEle = this.win.content.childNodes[newValue];        
        this.setHighColor(newEle);                    
    }
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.setText = function(xVal)
{    
    var displayText = this.arrOptions[xVal][0];
    var primaryKey  = this.arrOptions[xVal][1];
    
    this.textBoxInput.value = displayText; // Set text value
        
    // Fire callback
    this.onTypeAheadSelected.call(this, new ajax.components.TypeAhead.onTypeAheadEventArgs(this, displayText, primaryKey));
    
    this.hideWindow();    
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.grabHighlighted = function()
{
    ajax.debug.Output.writeLn("Grabbing highlighted, this.currentValueSelected = " + this.currentValueSelected);
    if (this.currentValueSelected >= 0)
    {                
        xVal = this.win.content.childNodes[this.currentValueSelected].theArrayNumber;
        ajax.debug.Output.writeLn("xVal = " + xVal);
        this.setText(xVal);        
    }
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.setRegExpressions = function()
{
    if (this.extendedProperties.caseSensitive == false)
        this.regExFlags = "i";
    else
        this.regExFlags = "";
    
    if (this.extendedProperties.matchAnywhere == false)
        this.regExAny = "^";
    else
        this.regExAny = "";
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.toString = function()
{
    return "ajax.components.TypeAhead";
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.prototype.toClassName = function()
{
    return ajax.lang.getClassName("ajax.components.TypeAhead");
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.cast = function(obj)
{///<returns type="ajax.components.TypeAhead"/>
    return obj;        
}


//-----------------------------------------------------------------------------
// sub classes
ajax.components.TypeAhead.onTypeAheadEventArgs = function(sender, displayText, primaryKey)
{
    this.sender      = sender;
    this.displayText = displayText;
    this.primaryKey  = primaryKey;
}
ajax.components.TypeAhead.onTypeAheadEventArgs.prototype = 
{
    sender: null,
    displayText: "",
    primaryKey: 0
};
//-----------------------------------------------------------------------------
ajax.components.TypeAhead.onTypeAheadEventArgs.cast = function(obj)
{///<returns type="ajax.components.TypeAhead.onTypeAheadEventArgs"/>
    return obj;        
}


ajax.components.TypeAhead.onListItemEventArgs = function(sender, listItem, displayText, primaryKey)
{
    this.sender     = sender;
    this.listItem   = listItem;
    this.displayText=displayText;
    this.primaryKey = primaryKey;
}
ajax.components.TypeAhead.onListItemEventArgs.prototype = 
{
    sender: null,
    listItem: null,
    displayText: "",
    primaryKey: 0
};
//-----------------------------------------------------------------------------
ajax.components.TypeAhead.onListItemEventArgs.cast = function(obj)
{///<returns type="ajax.components.TypeAhead.onListItemEventArgs"/>
    return obj;        
}



//-----------------------------------------------------------------------------
// Static function objects
ajax.components.TypeAhead.DefaultExtendedProperties = function() {}
ajax.components.TypeAhead.DefaultExtendedProperties.prototype = 
{
    // Public vars
    enabled                : true,
    caseSensitive          : false,
    matchAnywhere          : true,
    multi                  : true,
    matchTextboxWidth      : false,
    showNoMatchMessage     : true,
    noMatchingDataMessage  : "No Matching Data",
    useTimeout             : false,
    theVisibleTime         : 5000
    
    // regExFlags = "i" (if this.caseSensitive = false)
    // regExAny = "^" (if this.matchAnywhere = false)
};

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.DefaultExtendedProperties.cast = function(obj)
{///<returns type="ajax.components.TypeAhead.DefaultExtendedProperties"/>
    return obj;        
}


ajax.components.TypeAhead.ExtendedProperties = new ajax.components.TypeAhead.DefaultExtendedProperties();
ajax.components.TypeAhead.ShareExtendedProperties = false;
ajax.components.TypeAhead.setShareExtendedProperties = function(val)
{
    ajax.components.TypeAhead.ShareExtendedProperties = val;    
}


//-----------------------------------------------------------------------------
// Inner Classes...

// Subclass of ConfigurationManager
ajax.components.TypeAhead.ConfigurationManager = function(parentObject)
{
    ajax.components.TypeAhead.ConfigurationManager.superConstructor.call(this, parentObject.doc);
    this.parentObject = parentObject;
    
    var self = this;
    
    // Since the type ahead can share its settings with other type aheads
    // We want to subscribe to a "shared" event router, one that will die when this object dies but 
    // be accessible by other Type Aheads we may be running at this time.
    // I've decided that we will attach this to the window we belong to, so any other type ahead object that 
    // shares this window will also recieve the events, but the event router itself will be destroyed
    // when (or indeed, if) the page is changed.
    // The event router will tell us when any instance of a Type ahead changes it's settings.
    // When this happens we must update our enabled property and our menu item "checks" to
    // reflect the changes
    var onTypeAheadSettingsUpdate = function(e)
    {            
        // check the values from the settings
        var props = self.parentObject.extendedProperties;    
        // check enabled status
        self.parentObject.setEnabled(props.enabled); 
        
        if (self.configurationMenu)
        {            
            self.configurationMenu.getMenuItem("MATCH").setChecked(props.matchAnywhere);            
            self.configurationMenu.getMenuItem("MULTI").setChecked(props.multi);   
            self.configurationMenu.getMenuItem("ENABLED").setChecked(props.enabled);       
            self.configurationMenu.getMenuItem("CASE").setChecked(props.caseSensitive);             
        }
    }    
    ajax.lang.getEvent(this.window, this.window, "onTypeAheadSettingsUpdate").addListener(onTypeAheadSettingsUpdate);
        
}
ajax.lang.extend(ajax.components.TypeAhead.ConfigurationManager, "ajax.components.ConfigurationManager");
// Overrides
// These 2 methods should be overridden to cherry pick what to export & import
ajax.components.TypeAhead.ConfigurationManager.prototype.exportConfiguration = function()
{
    // this is where custom properties can be cherry picked from any object into the "this.exported" object for saving
    this.exported.enabled           = this.parentObject.extendedProperties.enabled;
    this.exported.caseSensitive     = this.parentObject.extendedProperties.caseSensitive;
    this.exported.matchAnywhere     = this.parentObject.extendedProperties.matchAnywhere;
    this.exported.multi             = this.parentObject.extendedProperties.multi;
    
    // Fire the static onSettingsUpdate event
    this.window.onTypeAheadSettingsUpdate.call(this, this.exported);
}
ajax.components.TypeAhead.ConfigurationManager.prototype.importConfiguration = function()
{
    // This is where custom properties can be imported from the "this.imported" object
    this.copyProperties(this.parentObject.extendedProperties, this.imported);
    
    // Fire the static onSettingsUpdate
    this.window.onTypeAheadSettingsUpdate.call(this, this.imported);
}
ajax.components.TypeAhead.ConfigurationManager.prototype.createConfigurationMenu = function()
{
    var self = this;
    
    // Create Menu
    var menuId = this.parentObject.name + "configurationMenu";    
    ajax.lang.safeDestroy(this.doc.getElementById(menuId));
    this.configurationMenu = ajax.lang.safeNew("ajax.components.Menu", this.doc, menuId, 200, 160);
    this.configurationMenu.animated = true;
    this.configurationMenu.setWideBackground(true);
    
    // handlers for each option
    var onEnabledSelectDelegate = function(e)
    {                   
        ajax.debug.Output.writeLn("You clicked on the Enabled Option: " + this.checked);
        self.parentObject.setEnabled(this.checked);
        self.saveConfiguration();        
        self.configurationMenu.hide();
    }
    var onMatchAnywhereSelectDelegate = function(e)
    {            
        ajax.debug.Output.writeLn("You clicked on the Match Anywhere option: " + this.checked);
        self.parentObject.extendedProperties.matchAnywhere = this.checked;   
        self.saveConfiguration();          
        self.configurationMenu.hide();                   
    }
    var onMultiSelectDelegate = function(e)
    {
        ajax.debug.Output.writeLn("You click on the Multi option: " + this.checked);
        self.parentObject.extendedProperties.multi = this.checked;        
        self.saveConfiguration();
        self.configurationMenu.hide();
    }    
    var onCaseSelectDelegate = function(e)
    {
        ajax.debug.Output.writeLn("You clicked on the Case Sensitive option: " + this.checked);
        self.parentObject.extendedProperties.caseSensitive = this.checked;  
        self.saveConfiguration();      
        self.configurationMenu.hide();
    }
    var onDefaultSelectDelegate = function(e)
    {       
        ajax.debug.Output.writeLn("You clicked on the Default option: " + this.checked);
        // COPY in the default properties ( so the pointer remains either at the common location
        // or our own extended property object as that was decided in the constructor.
        var defaultProperties = new ajax.components.TypeAhead.DefaultExtendedProperties();
        self.copyProperties(self.parentObject.extendedProperties, defaultProperties);
        // Save config & hide menu
        self.saveConfiguration();      
        self.configurationMenu.hide();
    }
    
    // Create Menu Items
    var displayFlags = ajax.components.MenuItemDisplayFlags;
    var flags = displayFlags.Image | displayFlags.Text | displayFlags.Checkbox;
    
    var menuItemMatchAnywhere   = ajax.lang.safeNew("ajax.components.MenuItem",
        this.doc, "Match Anywhere", "", "MATCH", null, flags, this.parentObject.extendedProperties.matchAnywhere);              
    var menuItemMulti           = ajax.lang.safeNew("ajax.components.MenuItem",
        this.doc, "Multi Placed Strings", "", "MULTI", null, flags, this.parentObject.extendedProperties.multi);    
    var itemLine            = ajax.lang.safeNew("ajax.components.MenuItem", this.doc, "-");
    var menuItemEnabled         = ajax.lang.safeNew("ajax.components.MenuItem",
        this.doc, "Enabled", "", "ENABLED", null, flags, this.parentObject.extendedProperties.enabled);    
    var menuItemCase            = ajax.lang.safeNew("ajax.components.MenuItem",
        this.doc, "Case Sensitive", "", "CASE", null, flags, this.parentObject.extendedProperties.caseSensitive);
    var itemLine2           = ajax.lang.safeNew("ajax.components.MenuItem", this.doc, "-");
    
    var menuItemDefault         = ajax.lang.safeNew("ajax.components.MenuItem",
        this.doc, "Default Settings", "", "DEFAULT", null, flags, this.parentObject.extendedProperties.caseSensitive);
    // We do not use the checkbox - but have the flag to display it to get it to line up with the others.
    menuItemDefault.setCheckEnabled(false);
        
        
    // Attach event handlers
    menuItemCase.getEvent(this.window, "onSelected").addListener(onCaseSelectDelegate);
    menuItemMatchAnywhere.getEvent(this.window, "onSelected").addListener(onMatchAnywhereSelectDelegate);
    menuItemMulti.getEvent(this.window, "onSelected").addListener(onMultiSelectDelegate);
    menuItemEnabled.getEvent(this.window, "onSelected").addListener(onEnabledSelectDelegate);    
    menuItemDefault.getEvent(this.window, "onSelected").addListener(onDefaultSelectDelegate);
    
    // Attach Items to menu
    this.configurationMenu.addMenuItem(menuItemCase);
    this.configurationMenu.addMenuItem(menuItemMatchAnywhere);
    this.configurationMenu.addMenuItem(menuItemMulti);
    this.configurationMenu.addMenuItem(itemLine);
    this.configurationMenu.addMenuItem(menuItemEnabled);     
    this.configurationMenu.addMenuItem(itemLine2);
    this.configurationMenu.addMenuItem(menuItemDefault);  
    
    // Add a delegate to the document mouseup to hide the menu
    ajax.lang.getEvent(this.window, this.doc, "onmouseup").addListener(function(e)
        {
            self.configurationMenu.hide();
        }
    );        
}

//-----------------------------------------------------------------------------
ajax.components.TypeAhead.ConfigurationManager.cast = function(obj)
{///<returns type="ajax.components.TypeAhead.ConfigurationManager"/>
    return obj;                
}